home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / software-center / softwarecenter / app.py < prev    next >
Text File  |  2009-10-23  |  20KB  |  502 lines

  1. # Copyright (C) 2009 Canonical
  2. #
  3. # Authors:
  4. #  Michael Vogt
  5. #
  6. # This program is free software; you can redistribute it and/or modify it under
  7. # the terms of the GNU General Public License as published by the Free Software
  8. # Foundation; version 3.
  9. #
  10. # This program is distributed in the hope that it will be useful, but WITHOUT
  11. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  13. # details.
  14. #
  15. # You should have received a copy of the GNU General Public License along with
  16. # this program; if not, write to the Free Software Foundation, Inc.,
  17. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18.  
  19. import apt
  20. import aptdaemon
  21. import locale
  22. import dbus
  23. import dbus.service
  24. import gettext
  25. import locale
  26. import logging
  27. import glib
  28. import gtk
  29. import os
  30. import subprocess
  31. import sys
  32. import xapian
  33.  
  34. from SimpleGtkbuilderApp import SimpleGtkbuilderApp
  35.  
  36. from softwarecenter.enums import *
  37. from softwarecenter.version import *
  38. from softwarecenter.db.database import StoreDatabase
  39.  
  40. import view.dialogs
  41. from view.viewswitcher import ViewSwitcher, ViewSwitcherList
  42. from view.pendingview import PendingView
  43. from view.installedpane import InstalledPane
  44. from view.availablepane import AvailablePane
  45. from view.softwarepane import SoftwarePane
  46.  
  47. from apt.aptcache import AptCache
  48. from gettext import gettext as _
  49.  
  50. class SoftwarecenterDbusController(dbus.service.Object):
  51.     """ 
  52.     This is a helper to provide the SoftwarecenterIFace
  53.     
  54.     It provides 
  55.     """
  56.     def __init__(self, parent, bus_name,
  57.                  object_path='/com/ubuntu/Softwarecenter'):
  58.         dbus.service.Object.__init__(self, bus_name, object_path)
  59.         self.parent = parent
  60.  
  61.     @dbus.service.method('com.ubuntu.SoftwarecenterIFace')
  62.     def bringToFront(self):
  63.         self.parent.window_main.present()
  64.         return True
  65.  
  66. class SoftwareCenterApp(SimpleGtkbuilderApp):
  67.     
  68.     (NOTEBOOK_PAGE_AVAILABLE,
  69.      NOTEBOOK_PAGE_INSTALLED,
  70.      NOTEBOOK_PAGE_PENDING) = range(3)
  71.  
  72.     WEBLINK_URL = "http://apt.ubuntu.com/p/%s"
  73.  
  74.     def __init__(self, datadir, xapian_base_path):
  75.         SimpleGtkbuilderApp.__init__(self, 
  76.                                      datadir+"/ui/SoftwareCenter.ui", 
  77.                                      "software-center")
  78.         gettext.bindtextdomain("software-center", "/usr/share/locale")
  79.         gettext.textdomain("software-center")
  80.         try:
  81.             locale.setlocale(locale.LC_ALL, "")
  82.         except:
  83.             logging.exception("setlocale failed")
  84.  
  85.         # setup dbus and exit if there is another instance already
  86.         # running
  87.         self.setup_dbus_or_bring_other_instance_to_front()
  88.         self.setup_database_rebuilding_listener()
  89.         
  90.         try:
  91.             locale.setlocale(locale.LC_ALL, "")
  92.         except Exception, e:
  93.             logging.exception("setlocale failed")
  94.  
  95.         # xapian
  96.         pathname = os.path.join(xapian_base_path, "xapian")
  97.         try:
  98.             self.db = StoreDatabase(pathname)
  99.             self.db.open()
  100.         except xapian.DatabaseOpeningError:
  101.             # Couldn't use that folder as a database
  102.             # This may be because we are in a bzr checkout and that
  103.             #   folder is empty. If the folder is empty, and we can find the
  104.             # script that does population, populate a database in it.
  105.             if os.path.isdir(pathname) and not os.listdir(pathname):
  106.                 from softwarecenter.db.update import rebuild_database
  107.                 logging.info("building local database")
  108.                 rebuild_database(pathname)
  109.                 self.db = StoreDatabase(pathname)
  110.                 self.db.open()
  111.         except xapian.DatabaseCorruptError, e:
  112.             logging.exception("xapian open failed")
  113.             view.dialogs.error(None, 
  114.                                _("Sorry, can not open the software database"),
  115.                                _("Please re-install the 'software-center' "
  116.                                  "package."))
  117.             # FIXME: force rebuild by providing a dbus service for this
  118.             sys.exit(1)
  119.     
  120.         # additional icons come from app-install-data
  121.         self.icons = gtk.icon_theme_get_default()
  122.         self.icons.append_search_path(ICON_PATH)
  123.         self.icons.append_search_path(SOFTWARE_CENTER_ICON_PATH)
  124.         # HACK: make it more friendly for local installs (for mpt)
  125.         self.icons.append_search_path(datadir+"/icons/32x32/status")
  126.         
  127.         # a main iteration friendly apt cache
  128.         self.cache = AptCache()
  129.  
  130.         # misc state
  131.         self._pending_transactions = 0
  132.         self._block_menuitem_view = False
  133.         self._available_items_for_page = {}
  134.         # FIXME: make this all part of a application object
  135.         self._selected_pkgname_for_page = {}
  136.         self._selected_appname_for_page = {}
  137.  
  138.         # available pane
  139.         self.available_pane = AvailablePane(self.cache, self.db,
  140.                                             self.icons, datadir)
  141.         self.available_pane.app_details.connect("selected", 
  142.                                                 self.on_app_details_changed,
  143.                                                 self.NOTEBOOK_PAGE_AVAILABLE)
  144.         self.available_pane.app_view.connect("application-selected",
  145.                                              self.on_app_selected,
  146.                                              self.NOTEBOOK_PAGE_AVAILABLE)
  147.         self.available_pane.connect("app-list-changed", 
  148.                                     self.on_app_list_changed,
  149.                                     self.NOTEBOOK_PAGE_AVAILABLE)
  150.         self.alignment_available.add(self.available_pane)
  151.  
  152.         # installed pane
  153.         self.installed_pane = InstalledPane(self.cache, self.db,
  154.                                             self.icons, datadir)
  155.         self.installed_pane.app_details.connect("selected", 
  156.                                                 self.on_app_details_changed,
  157.                                                 self.NOTEBOOK_PAGE_INSTALLED)
  158.         self.installed_pane.app_view.connect("application-selected",
  159.                                              self.on_app_selected,
  160.                                              self.NOTEBOOK_PAGE_INSTALLED)
  161.         self.installed_pane.connect("app-list-changed", 
  162.                                     self.on_app_list_changed,
  163.                                     self.NOTEBOOK_PAGE_INSTALLED)
  164.         self.alignment_installed.add(self.installed_pane)
  165.  
  166.         # pending view
  167.         self.pending_view = PendingView(self.icons)
  168.         self.scrolledwindow_transactions.add(self.pending_view)
  169.  
  170.         # view switcher
  171.         self.view_switcher = ViewSwitcher(datadir, self.icons)
  172.         self.scrolledwindow_viewswitcher.add(self.view_switcher)
  173.         self.view_switcher.show()
  174.         self.view_switcher.connect("view-changed", 
  175.                                    self.on_view_switcher_changed)
  176.         self.view_switcher.model.connect("transactions-changed", 
  177.                                    self.on_view_switcher_transactions_changed)
  178.         self.view_switcher.set_view(ViewSwitcherList.ACTION_ITEM_AVAILABLE)
  179.  
  180.         # launchpad integration help, its ok if that fails
  181.         try:
  182.             import LaunchpadIntegration
  183.             LaunchpadIntegration.set_sourcepackagename("software-center")
  184.             LaunchpadIntegration.add_items(self.menu_help, 1, True, False)
  185.         except Exception, e:
  186.             logging.debug("launchpad integration error: '%s'" % e)
  187.  
  188.         # default focus
  189.         self.available_pane.searchentry.grab_focus()
  190.  
  191.     # callbacks
  192.     def on_app_details_changed(self, widget, appname, pkgname, page):
  193.         self._selected_pkgname_for_page[page] = pkgname
  194.         self._selected_appname_for_page[page] = appname
  195.         self.update_app_status_menu()
  196.         self.update_status_bar()
  197.  
  198.     def on_app_list_changed(self, pane, new_len, page):
  199.         self._available_items_for_page[page] = new_len
  200.         if self.notebook_view.get_current_page() == page:
  201.             self.update_status_bar()
  202.  
  203.     def on_app_selected(self, widget, appname, pkgname, page):
  204.         self._selected_appname_for_page[page] = appname
  205.         self._selected_pkgname_for_page[page] = pkgname
  206.         self.menuitem_copy.set_sensitive(True)
  207.         self.menuitem_copy_web_link.set_sensitive(True)
  208.  
  209.     def on_window_main_delete_event(self, widget, event):
  210.         gtk.main_quit()
  211.         
  212.     def on_view_switcher_transactions_changed(self, view_switcher, pending_nr):
  213.         # if the pending number drops to zero check if we should switch
  214.         # to the previous view
  215.         if pending_nr == 0 and self._view_before_pending_switch is not None:
  216.             if self.view_switcher.get_view() is None:
  217.                 self.view_switcher.set_view(self._view_before_pending_switch)
  218.             self._view_before_pending_switch = None
  219.         # the spec says that on the first transaction it should auto-switch
  220.         # to the progress view
  221.         elif pending_nr > 0 and self._pending_transactions == 0:
  222.             self._view_before_pending_switch = self.view_switcher.get_view()
  223.             self.view_switcher.set_view(ViewSwitcherList.ACTION_ITEM_PENDING)
  224.         self._pending_transactions = pending_nr
  225.  
  226.     def on_view_switcher_changed(self, view_switcher, action):
  227.         logging.debug("view_switcher_activated: %s %s" % (view_switcher,action))
  228.         if action == self.NOTEBOOK_PAGE_AVAILABLE:
  229.             self.active_pane = self.available_pane
  230.         elif action == self.NOTEBOOK_PAGE_INSTALLED:
  231.             self.active_pane = self.installed_pane
  232.         elif action == self.NOTEBOOK_PAGE_PENDING:
  233.             self.active_pane = None
  234.         else:
  235.             assert False, "Not reached"
  236.         # set menu sensitve
  237.         self.menuitem_view_supported_only.set_sensitive(self.active_pane != None)
  238.         self.menuitem_view_all.set_sensitive(self.active_pane != None)
  239.         # set menu state
  240.         if self.active_pane:
  241.             self._block_menuitem_view = True
  242.             if self.active_pane.apps_filter.get_supported_only():
  243.                 self.menuitem_view_supported_only.activate()
  244.             else:
  245.                 self.menuitem_view_all.activate()
  246.             self._block_menuitem_view = False
  247.         # switch to new page
  248.         self.notebook_view.set_current_page(action)
  249.         self.update_status_bar()
  250.         self.update_app_status_menu()
  251.  
  252.     # Menu Items
  253.  
  254.     def on_menuitem_install_activate(self, menuitem):
  255.         self.active_pane.app_details.install()
  256.  
  257.     def on_menuitem_remove_activate(self, menuitem):
  258.         self.active_pane.app_details.remove()
  259.         
  260.     def on_menuitem_close_activate(self, widget):
  261.         gtk.main_quit()
  262.  
  263.     def on_menu_edit_activate(self, menuitem):
  264.         """
  265.         Check whether the search field is focused and if so, focus some items
  266.         """
  267.         if self.active_pane:
  268.             state = self.active_pane.searchentry.is_focus()
  269.         else:
  270.             state = False
  271.         edit_menu_items = [self.menuitem_undo, 
  272.                            self.menuitem_redo, 
  273.                            self.menuitem_cut, 
  274.                            self.menuitem_copy, 
  275.                            self.menuitem_paste, 
  276.                            self.menuitem_delete, 
  277.                            self.menuitem_select_all]
  278.         for item in edit_menu_items:
  279.             item.set_sensitive(state)
  280.  
  281.     def on_menuitem_undo_activate(self, menuitem):
  282.         self.active_pane.searchentry.undo()
  283.         
  284.     def on_menuitem_redo_activate(self, menuitem):
  285.         self.active_pane.searchentry.redo()
  286.  
  287.     def on_menuitem_cut_activate(self, menuitem):
  288.         self.active_pane.searchentry.cut_clipboard()
  289.  
  290.     def on_menuitem_copy_activate(self, menuitem):
  291.         self.active_pane.searchentry.copy_clipboard()
  292.  
  293.     def on_menuitem_paste_activate(self, menuitem):
  294.         self.active_pane.searchentry.paste_clipboard()
  295.  
  296.     def on_menuitem_delete_activate(self, menuitem):
  297.         self.active_pane.searchentry.set_text("")
  298.  
  299.     def on_menuitem_select_all_activate(self, menuitem):
  300.         self.active_pane.searchentry.select_region(0, -1)
  301.  
  302.     def on_menuitem_copy_web_link_activate(self, menuitem):
  303.         page = self.notebook_view.get_current_page()
  304.         try:
  305.             pkg = self._selected_pkgname_for_page[page]
  306.         except KeyError, e:
  307.             return
  308.         clipboard = gtk.Clipboard()
  309.         clipboard.set_text(self.WEBLINK_URL % pkg)
  310.  
  311.     def on_menuitem_search_activate(self, widget):
  312.         if self.active_pane:
  313.             self.active_pane.searchentry.grab_focus()
  314.             self.active_pane.searchentry.select_region(0, -1)
  315.  
  316.     def on_menuitem_software_sources_activate(self, widget):
  317.         #print "on_menu_item_software_sources_activate"
  318.         self.window_main.set_sensitive(False)
  319.         # run software-properties-gtk
  320.         p = subprocess.Popen(
  321.             ["gksu",
  322.              "--desktop", "/usr/share/applications/software-properties.desktop",
  323.              "--",
  324.              "/usr/bin/software-properties-gtk", 
  325.              "-n", 
  326.              "-t", str(self.window_main.window.xid)])
  327.         # wait for it to finish
  328.         ret = None
  329.         while ret is None:
  330.             while gtk.events_pending():
  331.                 gtk.main_iteration()
  332.             ret = p.poll()
  333.         # return code of 1 means that it changed
  334.         if ret == 1:
  335.             self.run_update_cache()
  336.         self.window_main.set_sensitive(True)
  337.  
  338.     def on_menuitem_about_activate(self, widget):
  339.         self.aboutdialog.set_version(VERSION)
  340.         self.aboutdialog.run()
  341.         self.aboutdialog.hide()
  342.  
  343.     def on_menuitem_help_activate(self, menuitem):
  344.         # run yelp
  345.         p = subprocess.Popen(["yelp","ghelp:software-center"])
  346.         # collect the exit status (otherwise we leave zombies)
  347.         glib.timeout_add(1000, lambda p: p.poll() == None, p)
  348.  
  349.     def on_menuitem_view_all_activate(self, widget):
  350.         if self._block_menuitem_view:
  351.             return
  352.         self.active_pane.apps_filter.set_supported_only(False)
  353.         self.active_pane.refresh_apps()
  354.  
  355.     def on_menuitem_view_supported_only_activate(self, widget):
  356.         if self._block_menuitem_view: 
  357.             return
  358.         self.active_pane.apps_filter.set_supported_only(True)
  359.         self.active_pane.refresh_apps()
  360.  
  361.     # helper
  362.  
  363.     # FIXME: move the two functions below into generic code
  364.     #        and share that with the appdetailsview
  365.     def _on_trans_finished(self, trans, enum):
  366.         """callback when a aptdaemon transaction finished"""
  367.         if enum == aptdaemon.enums.EXIT_FAILED:
  368.             excep = trans.get_error()
  369.             msg = "%s: %s\n%s\n\n%s" % (
  370.                    _("ERROR"),
  371.                    aptdaemon.enums.get_error_string_from_enum(excep.code),
  372.                    aptdaemon.enums.get_error_description_from_enum(excep.code),
  373.                    excep.details)
  374.             print msg
  375.         # re-open cache and refresh app display
  376.         self.cache.open()
  377.     def run_update_cache(self):
  378.         """update the apt cache (e.g. after new sources where added """
  379.         aptd_client = aptdaemon.client.AptClient()
  380.         trans = aptd_client.update_cache(exit_handler=self._on_trans_finished)
  381.         try:
  382.             trans.run()
  383.         except dbus.exceptions.DBusException, e:
  384.             if e._dbus_error_name == "org.freedesktop.PolicyKit.Error.NotAuthorized":
  385.                 pass
  386.             else:
  387.                 raise
  388.  
  389.     def update_app_status_menu(self):
  390.         """Helper that updates the 'File' and 'Edit' menu to enable/disable
  391.            install/remove and Copy/Copy weblink
  392.         """
  393.         logging.debug("update_app_status_menu")
  394.         # check if we have a pkg for this page
  395.         page = self.notebook_view.get_current_page()
  396.         try:
  397.             pkgname = self._selected_pkgname_for_page[page]
  398.         except KeyError, e:
  399.             self.menuitem_install.set_sensitive(False)
  400.             self.menuitem_remove.set_sensitive(False)
  401.             self.menuitem_copy_web_link.set_sensitive(False)
  402.             return False
  403.         # wait for the cache to become ready (if needed)
  404.         if not self.cache.ready:
  405.             glib.timeout_add(100, lambda: self.update_app_status_menu())
  406.             return False
  407.         # if the pkg is not in the cache, clear menu
  408.         if not self.cache.has_key(pkgname):
  409.             self.menuitem_install.set_sensitive(False)
  410.             self.menuitem_remove.set_sensitive(False)
  411.             self.menuitem_copy_web_link.set_sensitive(False)
  412.         # update File menu status
  413.         if self.cache.has_key(pkgname):
  414.             pkg = self.cache[pkgname]
  415.             installed = bool(pkg.installed)
  416.             self.menuitem_install.set_sensitive(not installed)
  417.             self.menuitem_remove.set_sensitive(installed)
  418.         else:
  419.             self.menuitem_install.set_sensitive(False)
  420.             self.menuitem_remove.set_sensitive(False)
  421.         # return False to ensure that a possible glib.timeout_add ends
  422.         return False
  423.  
  424.     def update_status_bar(self):
  425.         "Helper that updates the status bar"
  426.         page = self.notebook_view.get_current_page()
  427.         if self.active_pane:
  428.             s = self.active_pane.get_status_text()
  429.         else:
  430.             # FIXME: deal with the pending view status
  431.             s = ""
  432.         self.label_status.set_text(s)
  433.  
  434.     def _on_database_rebuilding_handler(self, is_rebuilding):
  435.         logging.debug("_on_database_rebuilding_handler %s" % is_rebuilding)
  436.         self._database_is_rebuilding = is_rebuilding
  437.         self.window_rebuilding.set_transient_for(self.window_main)
  438.         self.window_rebuilding.set_title("")
  439.         self.window_main.set_sensitive(not is_rebuilding)
  440.         # show dialog about the rebuilding status
  441.         if is_rebuilding:
  442.             self.window_rebuilding.show()
  443.         else:
  444.             # we need to re-open when the database finished updating
  445.             self.db.reopen()
  446.             self.window_rebuilding.hide()
  447.  
  448.     def setup_database_rebuilding_listener(self):
  449.         """
  450.         Setup system bus listener for database rebuilding
  451.         """
  452.         self._database_is_rebuilding = False
  453.         # get dbus
  454.         try:
  455.             bus = dbus.SystemBus()
  456.         except:
  457.             logging.exception("could not get system bus")
  458.             return
  459.         # check if its currently rebuilding (most likely not, so we
  460.         # just ignore errors from dbus because the interface
  461.         try:
  462.             proxy_obj = bus.get_object("com.ubuntu.Softwarecenter",
  463.                                        "/com/ubuntu/Softwarecenter")
  464.             iface = dbus.Interface(proxy_obj, "com.ubuntu.Softwarecenter")
  465.             res = iface.IsRebuilding()
  466.             self._on_database_rebuilding_handler(res)
  467.         except Exception ,e:
  468.             logging.debug("query for the update-database exception '%s' (probably ok)" % e)
  469.  
  470.         # add signal handler
  471.         bus.add_signal_receiver(self._on_database_rebuilding_handler,
  472.                                 "DatabaseRebuilding",
  473.                                 "com.ubuntu.Softwarecenter")
  474.  
  475.     def setup_dbus_or_bring_other_instance_to_front(self):
  476.         """ 
  477.         This sets up a dbus listener
  478.         """
  479.         try:
  480.             bus = dbus.SessionBus()
  481.         except:
  482.             logging.exception("could not initiate dbus")
  483.             return
  484.         # if there is another Softwarecenter running bring it to front
  485.         # and exit, otherwise install the dbus controller
  486.         try:
  487.             proxy_obj = bus.get_object('com.ubuntu.Softwarecenter', 
  488.                                        '/com/ubuntu/Softwarecenter')
  489.             iface = dbus.Interface(proxy_obj, 'com.ubuntu.SoftwarecenterIFace')
  490.             iface.bringToFront()
  491.             sys.exit()
  492.         except dbus.DBusException, e:
  493.             bus_name = dbus.service.BusName('com.ubuntu.Softwarecenter',bus)
  494.             self.dbusControler = SoftwarecenterDbusController(self, bus_name)
  495.  
  496.     def run(self):
  497.         self.window_main.show_all()
  498.         SimpleGtkbuilderApp.run(self)
  499.  
  500.  
  501.  
  502.